package market

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// KlineProvider 抽象：按交易所获取K线
// 统一输出为 market.Kline 数组
// interval 例如 "3m"、"4h"
// limit 为返回条数上限
type KlineProvider interface {
	GetKlines(symbol, interval string, limit int) ([]Kline, error)
}

// KlineProviderOptions provider配置
// HTTPProxy 形如 "127.0.0.1:7890"；为空则使用环境代理或直连
// IsTestnet 控制使用测试网（如果交易所支持）
type KlineProviderOptions struct {
	HTTPProxy string
	IsTestnet bool
}

// NewKlineProvider 工厂：按交易所返回对应K线适配器
func NewKlineProvider(exchange string, opts KlineProviderOptions) KlineProvider {
	switch exchange {
	case "binance":
		return &BinanceKlineProvider{client: NewAPIClientWithTestnet(opts.HTTPProxy, opts.IsTestnet)}
	case "aster":
		return NewAsterKlineProvider(opts)
	case "hyperliquid":
		return NewHyperliquidKlineProvider(opts)
	default:
		// 其他交易所暂未实现，返回nil以便上层选择降级或跳过
		return nil
	}
}

// -------------------- Binance --------------------

type BinanceKlineProvider struct {
	client *APIClient
}

func (p *BinanceKlineProvider) GetKlines(symbol, interval string, limit int) ([]Kline, error) {
	// 直接复用现有APIClient（Binance REST）
	return p.client.GetKlines(symbol, interval, limit)
}

// -------------------- Aster DEX --------------------

type AsterKlineProvider struct {
	client  *http.Client
	baseURL string
	proxy   *url.URL
}

func NewAsterKlineProvider(opts KlineProviderOptions) *AsterKlineProvider {
	var tr *http.Transport
	if opts.HTTPProxy != "" {
		log.Printf("AsterKlineProvider: 使用 HTTP 代理: %s", opts.HTTPProxy)
		proxyURL, err := url.Parse("http://" + opts.HTTPProxy)
		if err != nil {
			log.Printf("警告: 解析 HTTP 代理 URL 失败，将使用环境代理或直连: %v", err)
			tr = &http.Transport{Proxy: http.ProxyFromEnvironment}
		} else {
			tr = &http.Transport{Proxy: http.ProxyURL(proxyURL)}
		}
	} else {
		tr = &http.Transport{Proxy: http.ProxyFromEnvironment}
		log.Println("AsterKlineProvider: 未配置 HTTP 代理，回退到环境代理或直连")
	}
	return &AsterKlineProvider{
		client: &http.Client{Timeout: 30 * time.Second, Transport: tr},
		baseURL: "https://fapi.asterdex.com",
	}
}

func (p *AsterKlineProvider) GetKlines(symbol, interval string, limit int) ([]Kline, error) {
	// Aster兼容Binance风格的K线接口
	// 优先尝试 /fapi/v1/klines
	endpoint := fmt.Sprintf("%s/fapi/v1/klines", p.baseURL)
	req, err := http.NewRequest("GET", endpoint, nil)
	if err != nil {
		return nil, err
	}
	q := req.URL.Query()
	q.Add("symbol", symbol)
	q.Add("interval", interval)
	q.Add("limit", strconv.Itoa(limit))
	req.URL.RawQuery = q.Encode()

	log.Printf("📈 [K线来源] Aster DEX %s/fapi/v1/klines?symbol=%s&interval=%s&limit=%d", p.baseURL, symbol, interval, limit)

	resp, err := p.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Aster DEX API错误: %s (%d) - %s", resp.Status, resp.StatusCode, string(body))
	}

	var raw []KlineResponse
	if err := jsonUnmarshal(body, &raw); err != nil {
		return nil, err
	}

	var klines []Kline
	for _, kr := range raw {
		k, err := parseKline(kr)
		if err != nil {
			log.Printf("解析Aster K线失败: %v", err)
			continue
		}
		klines = append(klines, k)
	}
	return klines, nil
}

// -------------------- Hyperliquid REST --------------------

type HyperliquidKlineProvider struct {
	client  *http.Client
	infoURL string // 完整 /info 端点
}

func NewHyperliquidKlineProvider(opts KlineProviderOptions) *HyperliquidKlineProvider {
	var tr *http.Transport
	if opts.HTTPProxy != "" {
		log.Printf("HyperliquidKlineProvider: 使用 HTTP 代理: %s", opts.HTTPProxy)
		proxyURL, err := url.Parse("http://" + opts.HTTPProxy)
		if err != nil {
			log.Printf("警告: 解析 HTTP 代理 URL 失败，将使用环境代理或直连: %v", err)
			tr = &http.Transport{Proxy: http.ProxyFromEnvironment}
		} else {
			tr = &http.Transport{Proxy: http.ProxyURL(proxyURL)}
		}
	} else {
		tr = &http.Transport{Proxy: http.ProxyFromEnvironment}
		log.Println("HyperliquidKlineProvider: 未配置 HTTP 代理，回退到环境代理或直连")
	}

	base := "https://api.hyperliquid.xyz/info"
	if opts.IsTestnet {
		base = "https://api.hyperliquid-testnet.xyz/info"
	}

	return &HyperliquidKlineProvider{
		client:  &http.Client{Timeout: 30 * time.Second, Transport: tr},
		infoURL: base,
	}
}

// hlCandle 项目内解析结构（对应官方响应字段）
type hlCandle struct {
	T int64  `json:"T"` // 结束时间(ms)
	C string `json:"c"`
	H string `json:"h"`
	I string `json:"i"`
	L string `json:"l"`
	N int    `json:"n"`
	O string `json:"o"`
	S string `json:"s"`
	TStart int64  `json:"t"` // 开始时间(ms)
	V string `json:"v"`
}

func (p *HyperliquidKlineProvider) GetKlines(symbol, interval string, limit int) ([]Kline, error) {
	// 标准化并转换为 HL coin（去掉USDT后缀）
	norm := Normalize(symbol)
	coin := convertToHyperliquidCoin(norm)

	// 计算时间范围（尽量贴近 limit 条）
	ivDur, ok := parseIntervalToDuration(interval)
	if !ok {
		return nil, fmt.Errorf("不支持的时间间隔: %s", interval)
	}
	end := time.Now().UnixMilli()
	start := end - int64(ivDur.Milliseconds())*int64(limit)

	// 首先尝试直接请求指定interval（如4h若不支持，将在回退逻辑处理）
	reqBody := map[string]interface{}{
		"type": "candleSnapshot",
		"req": map[string]interface{}{
			"coin":      coin,
			"interval":  interval,
			"startTime": start,
			"endTime":   end,
		},
	}
	payload, _ := json.Marshal(reqBody)

	log.Printf("📈 [K线来源] Hyperliquid REST %s type=candleSnapshot coin=%s interval=%s start=%d end=%d", p.infoURL, coin, interval, start, end)
	req, err := http.NewRequest("POST", p.infoURL, bytes.NewBuffer(payload))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := p.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Hyperliquid API错误: %s (%d) - %s", resp.Status, resp.StatusCode, string(body))
	}

	// 解析响应
	var candles []hlCandle
	if err := json.Unmarshal(body, &candles); err != nil {
		return nil, fmt.Errorf("解析candleSnapshot失败: %v", err)
	}

	// 如果请求的是4h但返回为空，回退到3m并聚合
	if len(candles) == 0 && interval == "4h" {
		return p.getKlinesByAggregation(coin, limit)
	}

	// 转换为通用Kline
	klines := make([]Kline, 0, len(candles))
	for _, c := range candles {
		open, _ := strconv.ParseFloat(c.O, 64)
		high, _ := strconv.ParseFloat(c.H, 64)
		low, _ := strconv.ParseFloat(c.L, 64)
		close, _ := strconv.ParseFloat(c.C, 64)
		vol, _ := strconv.ParseFloat(c.V, 64)

		klines = append(klines, Kline{
			OpenTime:  c.TStart,
			Open:      open,
			High:      high,
			Low:       low,
			Close:     close,
			Volume:    vol,
			CloseTime: c.T,
			// 以下HL未提供，置零或估算
			QuoteVolume:         0,
			Trades:              c.N,
			TakerBuyBaseVolume:  0,
			TakerBuyQuoteVolume: 0,
		})
	}

	return klines, nil
}

// 回退：用3m聚合得到4h
func (p *HyperliquidKlineProvider) getKlinesByAggregation(coin string, limit int) ([]Kline, error) {
	end := time.Now().UnixMilli()
	// 4h = 80 * 3m
	need3m := limit * 80
	if need3m > 5000 {
		need3m = 5000 // HL单次最多5000根
	}
	start := end - (time.Duration(need3m)*3*time.Minute).Milliseconds()

	reqBody := map[string]interface{}{
		"type": "candleSnapshot",
		"req": map[string]interface{}{
			"coin":      coin,
			"interval":  "3m",
			"startTime": start,
			"endTime":   end,
		},
	}
	payload, _ := json.Marshal(reqBody)

	log.Printf("📈 [K线来源] Hyperliquid REST(回退聚合) %s coin=%s interval=3m start=%d end=%d", p.infoURL, coin, start, end)
	req, err := http.NewRequest("POST", p.infoURL, bytes.NewBuffer(payload))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := p.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Hyperliquid API错误: %s (%d) - %s", resp.Status, resp.StatusCode, string(body))
	}

	var candles []hlCandle
	if err := json.Unmarshal(body, &candles); err != nil {
		return nil, fmt.Errorf("解析candleSnapshot失败(3m): %v", err)
	}

	// 先按时间排序（HL通常已按升序返回，这里稳妥处理）
	// 注：为了避免额外依赖，这里不显式排序，假定返回是升序。

	// 聚合到4h
	bucket := 4 * time.Hour
	klines := aggregateCandles(candles, bucket)

	// 只取最后 limit 条
	if len(klines) > limit {
		klines = klines[len(klines)-limit:]
	}
	return klines, nil
}

// -------------------- 辅助函数 --------------------

func convertToHyperliquidCoin(symbol string) string {
	// 标准化后若以USDT结尾，去掉
	if len(symbol) > 4 && symbol[len(symbol)-4:] == "USDT" {
		return symbol[:len(symbol)-4]
	}
	return symbol
}

func parseIntervalToDuration(iv string) (time.Duration, bool) {
	switch iv {
	case "1m":
		return time.Minute, true
	case "3m":
		return 3 * time.Minute, true
	case "5m":
		return 5 * time.Minute, true
	case "15m":
		return 15 * time.Minute, true
	case "30m":
		return 30 * time.Minute, true
	case "1h":
		return time.Hour, true
	case "4h":
		return 4 * time.Hour, true // 若HL不支持，将在回退逻辑处理
	default:
		return 0, false
	}
}

func aggregateCandles(candles []hlCandle, target time.Duration) []Kline {
	if len(candles) == 0 {
		return nil
	}
	bucketMs := int64(target / time.Millisecond)
	var result []Kline

	var curStart int64 = -1
	var open, high, low, close, vol float64
	var trades int

	flush := func(endMs int64) {
		if curStart < 0 {
			return
		}
		result = append(result, Kline{
			OpenTime:            curStart,
			Open:                open,
			High:                high,
			Low:                 low,
			Close:               close,
			Volume:              vol,
			CloseTime:           endMs,
			QuoteVolume:         0,
			Trades:              trades,
			TakerBuyBaseVolume:  0,
			TakerBuyQuoteVolume: 0,
		})
	}

	for i, c := range candles {
		// 计算该3m所在的4h桶起始
		startMs := (c.TStart / bucketMs) * bucketMs
		if startMs != curStart {
			// 新桶，先将旧桶写出
			if i != 0 {
				flush(c.TStart)
			}
			curStart = startMs
			open, _ = strconv.ParseFloat(c.O, 64)
			high, _ = strconv.ParseFloat(c.H, 64)
			low, _ = strconv.ParseFloat(c.L, 64)
			close, _ = strconv.ParseFloat(c.C, 64)
			vol, _ = strconv.ParseFloat(c.V, 64)
			trades = c.N
			continue
		}
		// 同桶内聚合
		vOpen, _ := strconv.ParseFloat(c.O, 64)
		vHigh, _ := strconv.ParseFloat(c.H, 64)
		vLow, _ := strconv.ParseFloat(c.L, 64)
		vClose, _ := strconv.ParseFloat(c.C, 64)
		vVol, _ := strconv.ParseFloat(c.V, 64)
		if vHigh > high {
			high = vHigh
		}
		if vLow < low {
			low = vLow
		}
		close = vClose
		vol += vVol
		trades += c.N
		_ = vOpen // open 只取桶第一根
	}
	// 写出最后一个桶
	if curStart >= 0 {
		flush(candles[len(candles)-1].T)
	}
	return result
}

// 轻量封装 json.Unmarshal，避免在本文件重复导入encoding/json
func jsonUnmarshal(data []byte, v interface{}) error {
	return jsonUnmarshalStd(data, v)
}

// 使用标准库实现，避免循环依赖
// 提供一个可替换点以便未来切换到更快的JSON库
func jsonUnmarshalStd(data []byte, v interface{}) error {
	return stdJSONUnmarshal(data, v)
}

// 标准库别名（此处只为清晰，保持可读性）
var stdJSONUnmarshal = func(data []byte, v interface{}) error {
	return json.Unmarshal(data, v)
}